
/* GCSx
** ENTITY.CPP
**
** Entity support (instance of a script) (no edit-specific version)
** Includes bytecode interpreter
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// @TODO: respect ref

Entity::Entity(int myId) { start_func
    id = myId;
    script = NULL;
    active = 0;
    scriptLocked = 0;
    scriptType = 0;
    ref = 0;
    runStatus = RUN_UNINIT;
    i = NULL;
    endOfLocals = 0;
    createStack(&stack);
    
    varTable[PROP_NAME].p = new string;
    varTable[PROP_SPRITE].p = NULL;
    varTable[PROP_PRIORITY].i = DEFAULT_PRIORITY;
}

Entity::~Entity() { start_func
    // @TODO: assert ref=0?
    wipeStack();
    destroyStack(&stack);
    setScript(NULL);
    // @TODO: this is only safe if we ensure to destroy entities before sprites
    setSprite(NULL);
    delete (string*)varTable[PROP_NAME].p;
}

void Entity::setSprite(Sprite* newSprite) { start_func
    if (varTable[PROP_SPRITE].p) --(((ObjectBase*)varTable[PROP_SPRITE].p)->ref);
    varTable[PROP_SPRITE].p = (ObjectBase*)newSprite;
    if (newSprite) ++newSprite->ref;
}

void Entity::setScript(Script* newScript) { start_func
    if ((script) && (scriptLocked))
        script->markUnlock();
    
    wipeStack();
    i = NULL;
    runStatus = RUN_UNINIT;
    scriptLocked = 0;

    script = newScript;
    if (script) scriptType = newScript->getId();

    if ((script) && (active))
        activateResource();        
}

void Entity::activateResource() { start_func
    if ((script) && (!scriptLocked)) {
        assert(!i);
        assert((runStatus == RUN_UNINIT) || (runStatus == RUN_STOPPED));
        // @TODO: throw_File
        script->markLock();
        script->link();
        scriptLocked = 1;
        if (runStatus == RUN_UNINIT) {
            i = script->getCode();
            runStatus = RUN_INIT;
        }
    }
}

void Entity::deactivateResource() { start_func
    if ((script) && (scriptLocked) && (!i)) {
        assert((runStatus == RUN_UNINIT) || (runStatus == RUN_STOPPED));
        script->markUnlock();
        scriptLocked = 0;
    }
}

void Entity::setActive() { start_func
    if (!active) {
        activateResource();
        active = 1;
    }
}

void Entity::setInactive() { start_func
    if (active) {
        deactivateResource();
        active = 0;
    }
}

/*
** BYTECODE INTERPRETER follows
**
** All functions except main loop can be considered throw_Interpret,
** therefore follow dynamic allocations CAREFULLY.
*/

// @TODO: consolidate duplicate opcode/etc functions as much as possible
// once mostly debugged and otherwise optimized (reduce space = optimize cache)

//
// State data- interpreter runs outside class to prevent passing 'this'
// and allow simple function tables; all tables and data together.
//

static Uint32 opdata = 0;
static Uint8 addrmode = 0;
static Uint32* i_p = NULL;
// A note on stack usage- an opcode should not push onto the stack until it's
//   done with it's operands, as we sometimes reference "popped" data directly
//   at it's original, technically-invalid position
static Stack stackP;
static Entity* entity = NULL;

static StackEntry* operand = NULL;
static StackEntry* operandGrab = NULL;
static StackEntry* operandWrite = NULL;
static Sint32 operandDataI = 0;
static BCfloat operandDataF = 0;
static const char* operandDataS = NULL;
static void* operandDataP = NULL;
// 'toDeref' should always contain anything needing dereferencing
#define MAX_TODEREF 8
// (room for at least 2 derefs per operand)
static StackEntry toDeref[MAX_TODEREF];
static StackEntry* toDerefPos = toDeref;
static StackEntry* toDerefLast = toDeref + MAX_TODEREF;
// Two-element array (flip-flops) to store temporary operands that
// DON'T need dereferencing (int, float)
static StackEntry tempOperand[2];
static int tempPos = 0;
// Pre-created 'empty' int/float/entity/object operands
static StackEntry emptyOperand[4];

typedef void (*OperandFunc)();
typedef void (*OperandFuncDeref)(StackEntry*);

// Operand function sequence- (/ means 'or')
//   opcodes[]
//   ->grabbers[] -> extractors[] / copiers[] -> extractors[]
//   ->grabbersStore[] -> converters[]
//   ->grabbersWrite[]
//   ->grabbersUse[] -> copiersUse[] / converters[]
// Anyone can call dereferencers[] or referencers[] as needed
// Anyone can call copiersUse[] with _UNDEF for existing type to create blank entries


// Function tables-
// Dereferencing table- Indexed via 8 bits of existing datatype
// Referencing- only intended to support basic types
// May clobber operand IF you call on a RETURN type
static OperandFuncDeref dereferencers[STACK_TYPE_COUNT];
static OperandFuncDeref referencers[STACK_TYPE_COUNT];
// Copy/conversion tables- Indexed via 4 bits of desired datatype concatenated with 4 bits of existing datatype
// copiersUse[] ensures the copy is a usable, properly ref counted StackEntry and does NOT put in "toderef"
// copiersUse[] with _UNDEF existing is gauranteed to just make a blank entry, so can be
// used for creating empties.; copiersUse[] expects it's result to be stored via assignment
// copiers[] calls extractors immediately after or instead.
static OperandFunc converters[256]; // into operand
static OperandFunc copiers[256]; // into operandData* (clobbers operandGrab/2)
static OperandFunc copiersUse[256]; // into operand
// Extractor table- indexed by 8 bit operand; don't call if called copiers[]
// @TODO: create specialized types for all grabbers[] that use extractors and
// remove this array? (some functions are still needed for copiers[] array)
static OperandFunc extractors[256]; // into operandData* (from operandGrab)
// Decoder (grabber) table- indexed by 8 bit operand- grabs const into operandData*
static OperandFunc grabbers[256]; // into operandData* (clobbers operandGrab/2)
// Grabs non-const, pointer in operand ready to store/modify directly
// Write "destroys" AND DEREFS original contents without conversion! and
// also does not garauntee that the 'type' field is correct.
static OperandFunc grabbersStore[256]; // into operand
static OperandFunc grabbersWrite[256]; // into operandWrite
// Grabs into a ready-to-use StackEntry in operand (proper ref counting and all)
static OperandFunc grabbersUse[256]; // into operand
// Opcode table
static OperandFunc opcodes[OP_LAST];

#ifdef INTERPRETASSERT
static void interpretError() { start_func
    interpretAssert(0);
}
static void interpretErrorDeref(StackEntry*) { start_func
    interpretAssert(0);
}
#else
#define interpretError NULL
#define interpretErrorDeref NULL
#endif

static void doNothing() { start_func
}

//
// dereferencers[]
//

// Dereferencing/destruction (the seDeref* functions already assert)

static void derefNothing(StackEntry* s) { start_func
}
static void derefString(StackEntry* s) { start_func
    seDerefString(s);
}
static void derefArray(StackEntry* s) { start_func
    seDerefArray(s);
}
static void derefHash(StackEntry* s) { start_func
    seDerefHash(s);
}
static void derefEntity(StackEntry* s) { start_func
    Entity::seDerefEntity(s);
}
static void derefObject(StackEntry* s) { start_func
    seDerefObject(s);
}
static void derefReturn(StackEntry* s) { start_func
    interpretAssert(s->type == STACK_RETURNPTR);

    StackEntry* target = (StackEntry*)(s->data.p);
    if (target) {
        interpretAssert(target->type & STACK_REPLYPTR);
        interpretAssert((target->type | ~STACK_REPLYPTR) <= STACK_BASETYPE);
        // Generate empty of requested type
        // Assumes STACK_UNDEF is 0 otherwise we should have | STACK_UNDEF here
        interpretAssert(STACK_UNDEF == 0);
        copiersUse[(target->type | ~STACK_REPLYPTR) << 4]();
        *target = *operand;
    }
}
static void derefReply(StackEntry* s) { start_func
    interpretAssert(s->type == STACK_REPLYPTR);

    StackEntry* target = (StackEntry*)(s->data.p);
    interpretAssert(target);
    interpretAssert(target->type == STACK_RETURNPTR);
    target->data.p = NULL;
}

//
// referencers[]
//

static void refNothing(StackEntry* s) { start_func
}
static void refString(StackEntry* s) { start_func
    s->data.p = new string(*(string*)s->data.p);
}
static void refArray(StackEntry* s) { start_func
    interpretAssert(s->type == STACK_ARRAY);
    refIncArray((Array*)s->data.p);
}
static void refHash(StackEntry* s) { start_func
    interpretAssert(s->type == STACK_HASH);
    refIncHash((Hash*)s->data.p);
}
static void refEntity(StackEntry* s) { start_func
    Entity::seRefEntity(s);
}
static void refObject(StackEntry* s) { start_func
    interpretAssert(s->type == STACK_OBJECT);
    if (s->data.p) ++(((ObjectBase*)s->data.p)->ref);
}

//
// converters[]
//

// Converting- dereferences old operand, replaces with new in-place

static void convertEmptyToInt() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    // (always deref, as it's not float/int at this point)
    dereferencers[operand->type](operand);
    operand->type = STACK_INT;
    operand->data.i = 0;
}
static void convertEmptyToFloat() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    operand->type = STACK_FLOAT;
    operand->data.f = 0.0;
}
static void convertEmptyToStr() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    operand->type = STACK_STRING;
    operand->data.p = new string(blankString);
}
static void convertEmptyToArray() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    // @TODO:
}
static void convertEmptyToHash() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    // @TODO:
}
static void convertEmptyToEntity() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    operand->type = STACK_ENTITY;
    operand->data.p = NULL;
}
static void convertEmptyToObject() { start_func
    interpretAssert(operand->type != STACK_RETURNPTR);
    dereferencers[operand->type](operand);
    operand->type = STACK_OBJECT;
    operand->data.p = NULL;
}

static void convertIntToFloat() { start_func
    operand->type = STACK_FLOAT;
    operand->data.f = (BCfloat)operand->data.i;
}
static void convertFloatToInt() { start_func
    operand->type = STACK_INT;
    operand->data.i = (Sint32)operand->data.f;
}
static void convertIntToString() { start_func
    operand->type = STACK_STRING;
    operand->data.p = new string(intToStr(operand->data.i));
}
static void convertStringToInt() { start_func
    operand->type = STACK_INT;
    string* myStr = (string*)operand->data.p;
    operand->data.i = strToInt(*myStr);
    delete myStr;
}
static void convertFloatToString() { start_func
    operand->type = STACK_STRING;
    operand->data.p = new string(floatToStr(operand->data.f));
}
static void convertStringToFloat() { start_func
    operand->type = STACK_FLOAT;
    string* myStr = (string*)operand->data.p;
    operand->data.f = strToFloat(*myStr);
    delete myStr;
}

//
// copiers[]
//

// Copying- ignores old operand, replaces with new in operandData* (doesn't deref old)
// New version is set up to deref for you automatically
// Handles the work of extractors[] too

static void copyCreateInt() { start_func
    operandDataI = emptyOperand[0].data.i;
}
static void copyCreateFloat() { start_func
    operandDataF = emptyOperand[1].data.f;
}
static void copyCreateStr() { start_func
    operandDataS = "";
}
static void copyCreateArray() { start_func
    // @TODO:
}
static void copyCreateHash() { start_func
    // @TODO:
}
static void copyCreateEntity() { start_func
    operandDataP = NULL;
}
static void copyCreateObject() { start_func
    operandDataP = NULL;
}

static void copyIntToFloat() { start_func
    operandDataF = (BCfloat)operandGrab->data.i;
}
static void copyFloatToInt() { start_func
    operandDataI = (Sint32)operandGrab->data.f;
}
static void copyIntToString() { start_func
    if (++toDerefPos == toDerefLast) toDerefPos = toDeref;
    interpretAssert(toDerefPos->type != STACK_RETURNPTR);
    dereferencers[toDerefPos->type](toDerefPos);
    // (need to set type to ensure dereferencing)
    toDerefPos->type = STACK_STRING;
    toDerefPos->data.p = new string(intToStr(toDerefPos->data.i));
    operandDataS = ((string*)toDerefPos->data.p)->c_str();
}
static void copyStringToInt() { start_func
    operandDataI = strToInt(*(string*)operandGrab->data.p);
}
static void copyFloatToString() { start_func
    if (++toDerefPos == toDerefLast) toDerefPos = toDeref;
    interpretAssert(toDerefPos->type != STACK_RETURNPTR);
    dereferencers[toDerefPos->type](toDerefPos);
    // (need to set type to ensure dereferencing)
    toDerefPos->type = STACK_STRING;
    toDerefPos->data.p = new string(floatToStr(operandGrab->data.f));
    operandDataS = ((string*)toDerefPos->data.p)->c_str();
}
static void copyStringToFloat() { start_func
    operandDataF = strToFloat(*(string*)operandGrab->data.p);
}

//
// copiersUse[]
//

// Copying for Use- ignores old operand, replaces with new in operand (doesn't deref old)
// 'Use' versions don't set new up to be deref'd either, expecting it will get stored VIA ASSIGNMENT

static void copyCreateIntUse() { start_func
    operand = emptyOperand;
}
static void copyCreateFloatUse() { start_func
    operand = emptyOperand + 1;
}
static void copyCreateStrUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_STRING;
    operand->data.p = new string;
}
static void copyCreateArrayUse() { start_func
    // @TODO:
}
static void copyCreateHashUse() { start_func
    // @TODO:
}
static void copyCreateEntityUse() { start_func
    operand = emptyOperand + 2;
}
static void copyCreateObjectUse() { start_func
    operand = emptyOperand + 3;
}

static void copyStringUse() { start_func
    string* prev = (string*)operand->data.p;
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_STRING;
    operand->data.p = new string(*prev);
}

static void copyIntToFloatUse() { start_func
    BCfloat result = (BCfloat)operand->data.i;
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_FLOAT;
    operand->data.f = result;
}
static void copyFloatToIntUse() { start_func
    Sint32 result = (Sint32)operand->data.f;
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_INT;
    operand->data.i = result;
}
static void copyIntToStringUse() { start_func
    Uint32 val = operand->data.i;
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_STRING;
    operand->data.p = new string(intToStr(val));
}
static void copyStringToIntUse() { start_func
    Sint32 result = strToInt(*(string*)operand->data.p);
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_INT;
    operand->data.i = result;
}
static void copyFloatToStringUse() { start_func
    BCfloat val = operand->data.f;
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_STRING;
    operand->data.p = new string(floatToStr(val));
}
static void copyStringToFloatUse() { start_func
    BCfloat result = strToFloat(*(string*)operand->data.p);
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_FLOAT;
    operand->data.f = result;
}

//
// extractors[]
//

// Extracting- always goes in operandData* off of operandGrab

static void extractInt() { start_func
    interpretAssert(operandGrab->type == STACK_INT);

    operandDataI = operandGrab->data.i;
}
static void extractFloat() { start_func
    interpretAssert(operandGrab->type == STACK_FLOAT);

    operandDataF = operandGrab->data.f;
}
static void extractStringConst() { start_func
    interpretAssert(operandGrab->type == STACK_STRING);

    operandDataS = ((string*)operandGrab->data.p)->c_str();
}
static void extractArray() { start_func
    interpretAssert(operandGrab->type == STACK_ARRAY);

    // @TODO:
}
static void extractHash() { start_func
    interpretAssert(operandGrab->type == STACK_HASH);

    // @TODO:
}
static void extractEntity() { start_func
    interpretAssert(operandGrab->type == STACK_ENTITY);

    operandDataP = operandGrab->data.p;
}
static void extractObject() { start_func
    interpretAssert(operandGrab->type == STACK_OBJECT);

    operandDataP = operandGrab->data.p;
}

//
// grabbers[]
//

// Grabbing into operandData*, via operandGrab/2

static void grabInt() { start_func
    operandDataI = *i_p++;
}
static void grabFloat() { start_func
    operandDataF = *((BCfloat*)i_p);
    i_p += bcFloatSize;
}
static void grabStringConst() { start_func
    operandDataS = (const char*)(i_p + *i_p);
    ++i_p;
}
static void grabThis() { start_func
    operandDataP = entity;
}
static void grabNothing() { start_func
    operandDataP = NULL;
}

static void grabStack() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operandGrab = stackP.top - 1 - *i_p++;
    extractors[addrmode]();
}
// Int/Float optimized due to commonality (@TODO: strconst? all!?)
static void grabStackInt() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandDataI = (stackP.top - 1 - *i_p++)->data.i;
}
static void grabStackFloat() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandDataF = (stackP.top - 1 - *i_p++)->data.f;
}
static void grabStackCopy() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operandGrab = stackP.top - 1 - *i_p++;
    interpretAssert(operandGrab->type <= 0x0F);
    // @TODO: can optimize (once we start testing copy/convert)
    // if operand->type is already in the 0xF0 nibble (we don't
    // use operand->type anywhere else but as a dereferencers[] index!)
    // OR flip addrmode! (addrmode is only used with OM_* for this reason!
    // but may be "converted" silently to STACK_* or DATA_* somewhere- see *Write functions)
    copiers[((addrmode & 0x0F) << 4) | operandGrab->type]();
}
static void grabLocal() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandGrab = stackP.data + *i_p++;
    extractors[addrmode]();
}
// Int/Float optimized due to commonality (@TODO: strconst? all!?)
static void grabLocalInt() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandDataI = (stackP.data + *i_p++)->data.i;
}
static void grabLocalFloat() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandDataF = (stackP.data + *i_p++)->data.f;
}
static void grabLocalCopy() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandGrab = stackP.data + *i_p++;
    interpretAssert(operandGrab->type <= 0x0F);
    copiers[((addrmode & 0x0F) << 4) | operandGrab->type]();
}
static void grabPop() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());

    if (++toDerefPos == toDerefLast) toDerefPos = toDeref;
    operandGrab = toDerefPos;
    interpretAssert(operandGrab->type != STACK_RETURNPTR);
    dereferencers[operandGrab->type](operandGrab);
    *operandGrab = *--stackP.top;
    extractors[addrmode]();
}
// Int/Float optimized due to commonality
// @TODO: versions for other types like strconst? (do for Local/Stack versions too)
static void grabPopInt() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());

    operandDataI = (--stackP.top)->data.i;
}
static void grabPopFloat() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());

    operandDataF = (--stackP.top)->data.f;
}
static void grabPopCopy() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());

    operandGrab = --stackP.top;
    interpretAssert(operandGrab->type <= 0x0F);
    // Instead of dereferencing immediately, we just add to the dereferencing array
    // so the copier has the most flexibility (to point to it still, etc.)
    if (++toDerefPos == toDerefLast) toDerefPos = toDeref;
    interpretAssert(toDerefPos->type != STACK_RETURNPTR);
    dereferencers[toDerefPos->type](toDerefPos);
    *toDerefPos = *operandGrab;
    copiers[((addrmode & 0x0F) << 4) | operandGrab->type]();
}

//
// grabbersStore[]
//

static void grabStackStore() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = stackP.top - 1 - *i_p++;
}
static void grabStackConvertStore() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = stackP.top - 1 - *i_p++;
    interpretAssert(operand->type <= 0x0F);
    converters[((addrmode & 0x0F) << 4) | operand->type]();
}
static void grabLocalStore() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operand = stackP.data + *i_p++;
}
static void grabLocalConvertStore() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operand = stackP.data + *i_p++;
    interpretAssert(operand->type <= 0x0F);
    converters[((addrmode & 0x0F) << 4) | operand->type]();
}

//
// grabbersWrite[]
//

static void grabStackNumWrite() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operandWrite = stackP.top - 1 - *i_p++;
}
static void grabStackWrite() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operandWrite = stackP.top - 1 - *i_p++;
    dereferencers[operandWrite->type](operandWrite);
}
static void grabLocalNumWrite() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandWrite = stackP.data + *i_p++;
}
static void grabLocalWrite() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operandWrite = stackP.data + *i_p++;
    dereferencers[operandWrite->type](operandWrite);
}

//
// grabbersUse[]
//

// Grabbing into StackEntry (properly referenced as it's own copy)

static void grabIntUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_INT;
    operand->data.i = *i_p++;
}
static void grabFloatUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_INT;
    operand->data.f = *((BCfloat*)i_p);
    i_p += bcFloatSize;
}
static void grabStringUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_STRING;
    operand->data.p = new string((const char*)(i_p + *i_p));
    ++i_p;
}
static void grabThisUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    operand->type = STACK_ENTITY;
    operand->data.p = entity;
    Entity::seRefEntity(operand);
}
static void grabNothingUse() { start_func
    operand = tempOperand + (tempPos ^= 1);
    // @TODO: faster to assign copy from empty operand?
    operand->type = STACK_ENTITY;
    operand->data.p = NULL;
}

static void grabStackUse() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = tempOperand + (tempPos ^= 1);
    *operand = *(stackP.top - 1 - *i_p++);
    referencers[operand->type](operand);
}
static void grabStackNumUse() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = stackP.top - 1 - *i_p++;
}
static void grabStackCopyUse() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = stackP.top - 1 - *i_p++;
    interpretAssert(operand->type <= 0x0F);
    copiersUse[((addrmode & 0x0F) << 4) | operand->type]();
}
static void grabLocalUse() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operand = tempOperand + (tempPos ^= 1);
    *operand = *(stackP.data + *i_p++);
    referencers[operand->type](operand);
}
static void grabLocalNumUse() { start_func
    interpretAssert((stackP.top - stackP.data) - *i_p > entity->getEndOfLocals());
    interpretAssert(*i_p < (Uint32)(stackP.top - stackP.data));

    operand = stackP.data + *i_p++;
}
static void grabLocalCopyUse() { start_func
    interpretAssert(*i_p < entity->getEndOfLocals());

    operand = stackP.data + *i_p++;
    interpretAssert(operand->type <= 0x0F);
    copiersUse[((addrmode & 0x0F) << 4) | operand->type]();
}
static void grabPopUse() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());
    
    operand = --stackP.top;
}
static void grabPopCopyUse() { start_func
    interpretAssert((Uint32)(stackP.top - stackP.data) > entity->getEndOfLocals());

    operand = --stackP.top;
    interpretAssert(operand->type <= 0x0F);
    converters[((addrmode & 0x0F) << 4) | operand->type]();
}

//
// Opcode functions
//

// Standard operand pulling, when it can't be optimized
#define OPERAND_0 \
    interpretAssert((opdata & 0xFFFFFF00) == 0);

#define ASSERT_1 \
    interpretAssert(opdata & 0xFF00); \
    interpretAssert((opdata & 0xFFFF0000) == 0);
#define OPERAND_1 \
    ASSERT_1 \
    grabbers[addrmode = opdata >> 8]();
    
#define ASSERT_2 \
    interpretAssert(opdata & 0xFF00); \
    interpretAssert(opdata & 0xFF0000); \
    interpretAssert((opdata & 0xFF000000) == 0);
#define OPERAND_2 \
    ASSERT_2 \
    grabbersStore[addrmode = opdata >> 8](); \
    grabbers[addrmode = opdata >> 16]();
#define OPERAND_2CI \
    ASSERT_2 \
    grabbers[addrmode = opdata >> 8](); \
    Sint32 operandData2I = operandDataI; \
    grabbers[addrmode = opdata >> 16]();
#define OPERAND_2CF \
    ASSERT_2 \
    grabbers[addrmode = opdata >> 8](); \
    BCfloat operandData2F = operandDataF; \
    grabbers[addrmode = opdata >> 16]();
#define OPERAND_2CS \
    ASSERT_2 \
    grabbers[addrmode = opdata >> 8](); \
    const char* operandData2S = operandDataS; \
    grabbers[addrmode = opdata >> 16]();
#define OPERAND_2CP \
    ASSERT_2 \
    grabbers[addrmode = opdata >> 8](); \
    void* operandData2P = operandDataP; \
    grabbers[addrmode = opdata >> 16]();

#define ASSERT_3 \
    interpretAssert(opdata & 0xFF00); \
    interpretAssert(opdata & 0xFF0000); \
    interpretAssert(opdata & 0xFF000000);
    
// Opcodes
static void op_noop() { start_func
    OPERAND_0
}
void Entity::op_init() { start_func
    interpretAssert(entity->runStatus == RUN_INIT);
    OPERAND_0
    
    entity->runStatus = RUN_ACTIVE;
    entity->endOfLocals = stackP.top - stackP.data;
}
void Entity::op_idle() { start_func
    OPERAND_0

    entity->runStatus = RUN_IDLE;
}
static void op_jump() { start_func
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    ASSERT_1

    // @TODO: + 1 when creating?
    i_p += *((Sint32*)i_p) + 1;
}
void Entity::op_ret() { start_func
    interpretAssert(stackP.top);
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    interpretAssert(*i_p + *(i_p + 1) + 2 <= stackP.top - stackP.data - entity->getEndOfLocals());
    ASSERT_2

    // @TODO: this is one opcode that's had no real optimization attempts and
    // yet is reasonably important and should be looked at in more detail due to
    // its length. One specific optimization that probably isn't worth it is if
    // both parameters are 0 AND there's no return pointer, we don't have to do
    // anything. This would be better off as another opcode.

    // Return value
    operand = --stackP.top;

    // Pop function locals
    Uint32 toKill = *i_p++;
    while (toKill--) {
        --stackP.top;
        interpretAssert(stackP.top->type != STACK_RETURNPTR);
        dereferencers[stackP.top->type](stackP.top);
    }

    // (# of parameters)
    toKill = *i_p;

    // Jump back
    i_p = (Uint32*)((--stackP.top)->data.p);
    // @TODO: optimize away RUN_STOPPED and just use i_p=NULL for stopped?
    // @TODO: if stopped, assert stack is locals only left
    if (!i_p)
        entity->runStatus = RUN_STOPPED;

    // Pop parameters
    while (toKill--) {
        --stackP.top;
        interpretAssert(stackP.top->type != STACK_RETURNPTR);
        dereferencers[stackP.top->type](stackP.top);
    }

    // If at least one stack entry left and it's a return ptr...
    // (we don't need to check for endoflocals as locals can never be return ptrs)
    if ((stackP.top != stackP.data) && (stackP.top[-1].type == STACK_RETURNPTR)) {
        // Return or destroy value
        StackEntry* target = (StackEntry*)((--stackP.top)->data.p);
        if (target) {
            interpretAssert(target->type & STACK_REPLYPTR);
            interpretAssert((target->type | ~STACK_REPLYPTR) <= STACK_BASETYPE);
            interpretAssert(((target->type | ~STACK_REPLYPTR) == operand->type) ||
                            ((target->type | ~STACK_REPLYPTR) == STACK_UNDEF));
            *target = *operand;
        }
        else {
            interpretAssert(operand->type != STACK_RETURNPTR);
            dereferencers[operand->type](operand);
        }
    }
    // Otherwise, push back onto stack UNLESS we stopped
    else {
        if (i_p) {
            *stackP.top++ = *operand;
        }
        else {
            interpretAssert(operand->type != STACK_RETURNPTR);
            dereferencers[operand->type](operand);
        }
    }
}
void Entity::op_retvoid() { start_func
    interpretAssert(stackP.top);
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    interpretAssert(*i_p + *(i_p + 1) + 1 <= stackP.top - stackP.data - entity->getEndOfLocals());
    ASSERT_2

    // @TODO: this is one opcode that's had no real optimization attempts and
    // yet is reasonably important and should be looked at in more detail due to
    // its length. One specific optimization that probably isn't worth it is if
    // both parameters are 0 AND there's no return pointer, we don't have to do
    // anything. This would be better off as another opcode.

    // Pop function locals
    Uint32 toKill = *i_p++;
    while (toKill--) {
        --stackP.top;
        interpretAssert(stackP.top->type != STACK_RETURNPTR);
        dereferencers[stackP.top->type](stackP.top);
    }

    // (# of parameters)
    toKill = *i_p;

    // Jump back
    i_p = (Uint32*)((--stackP.top)->data.p);
    // @TODO: optimize away RUN_STOPPED and just use i_p=NULL for stopped?
    // @TODO: if stopped, assert stack is locals only left
    if (!i_p)
        entity->runStatus = RUN_STOPPED;

    // Pop parameters
    while (toKill--) {
        --stackP.top;
        interpretAssert(stackP.top->type != STACK_RETURNPTR);
        dereferencers[stackP.top->type](stackP.top);
    }

    // If at least one stack entry left and it's a return ptr...
    // (we don't need to check for endoflocals as locals can never be return ptrs)
    if ((stackP.top != stackP.data) && (stackP.top[-1].type == STACK_RETURNPTR)) {
        // Return empty value
        StackEntry* target = (StackEntry*)((--stackP.top)->data.p);
        if (target) {
            interpretAssert(target->type & STACK_REPLYPTR);
            interpretAssert((target->type | ~STACK_REPLYPTR) <= STACK_BASETYPE);
            // Generate empty of requested type
            // Assumes STACK_UNDEF is 0 otherwise we should have | STACK_UNDEF here
            interpretAssert(STACK_UNDEF == 0);
            copiersUse[(target->type | ~STACK_REPLYPTR) << 4]();
            *target = *operand;
        }
    }
}
void Entity::op_stop() { start_func
    interpretAssert(entity->runStatus != RUN_INIT);
    OPERAND_0
    
    entity->runStatus = RUN_STOPPED;
    i_p = NULL;
    
    // wipe stack minus locals (small chance stack is completely empty)
    if (stackP.top) {
        Uint32 toKill = stackP.top - stackP.data - entity->endOfLocals;
        while (toKill--) {
            --stackP.top;
            // (return ptrs ok)
            dereferencers[stackP.top->type](stackP.top);
        }
    }
}
static void op_subr() { start_func
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    ASSERT_1

    prepStack(stackP);
    stackP.top->type = STACK_CODEPTR;
    // @TODO: perform + 1 on return? etc
    stackP.top++->data.p = i_p + 1;

    // @TODO: + 1 when creating?
    i_p += *((Sint32*)i_p) + 1;
}

void Entity::op_debug() { start_func
    OPERAND_1
    
    // No need to optimize this, and could turn up ANYTHING depending on
    // how we use it
    switch (addrmode) {
        // @TODO: remove the ones of these that can't appear anyway
        // @TODO: add script/object types to @/O
        case OM_ALL:
            debugWrite("%s: @ ALL", entity->getName()->c_str());
            break;
        case OM_ALL_OTHER:
            debugWrite("%s: @ ALL_OTHER", entity->getName()->c_str());
            break;
        case OM_NOTHING:
            debugWrite("%s: @ NOTHING", entity->getName()->c_str());
            break;
        case OM_THIS:
            debugWrite("%s: @ THIS", entity->getName()->c_str());
            break;
        case OM_POINTER:
            debugWrite("%s: * %p", entity->getName()->c_str(), operandDataS);
            break;

        default:
            switch (addrmode & 0x0F) {
                case OM_INT:
                    debugWrite("%s: # %d", entity->getName()->c_str(), operandDataI);
                    break;
                case OM_FLOAT:
                    debugWrite("%s: %% %"BC_FLOAT_PRINTF, entity->getName()->c_str(), operandDataF);
                    break;
                case OM_STR_CONST:
                    debugWrite("%s: $ %s", entity->getName()->c_str(), operandDataS);
                    break;
                case OM_ENTITY:
                    if (operandDataP)
                        debugWrite("%s: @ %s", entity->getName()->c_str(), ((Entity*)operandDataP)->getName()->c_str());
                    else
                        debugWrite("%s: @ NOTHING", entity->getName()->c_str());
                    break;
                case OM_OBJECT:
                    if (operandDataP)
                        debugWrite("%s: O %p", entity->getName()->c_str(), operandDataP);
                    else
                        debugWrite("%s: O NOTHING", entity->getName()->c_str());
                    break;
                case OM_ENTRY:
                case OM_ARRAY:
                case OM_HASH:
                    // @TODO:
                    break;
            }
            
            break;
    }
}

static void op_addI() { start_func
    OPERAND_2

    operand->data.i += operandDataI;
}
static void op_divI() { start_func
    OPERAND_2

    if (operandDataI)
        operand->data.i /= operandDataI;
}
static void op_modI() { start_func
    OPERAND_2

    if (operandDataI)
        operand->data.i %= operandDataI;
}
static void op_multI() { start_func
    OPERAND_2

    operand->data.i *= operandDataI;
}
static void op_subI() { start_func
    OPERAND_2

    operand->data.i -= operandDataI;
}

static void op_addF() { start_func
    OPERAND_2

    operand->data.f += operandDataF;
}
static void op_divF() { start_func
    OPERAND_2

    // (allow floating point div/0- results in INF)
    operand->data.f /= operandDataF;
}
static void op_multF() { start_func
    OPERAND_2

    operand->data.f *= operandDataF;
}
static void op_subF() { start_func
    OPERAND_2

    operand->data.f -= operandDataF;
}

static void op_andI() { start_func
    OPERAND_2

    operand->data.i &= operandDataI;
}
static void op_orI() { start_func
    OPERAND_2

    operand->data.i |= operandDataI;
}
static void op_shiftlI() { start_func
    OPERAND_2

    // Uint so bit shift operators do not sign-extend
    (Uint32&)operand->data.i <<= operandDataI;
}
static void op_shiftrI() { start_func
    OPERAND_2

    // Uint so bit shift operators do not sign-extend
    (Uint32&)operand->data.i >>= operandDataI;
}
static void op_xorI() { start_func
    OPERAND_2

    operand->data.i ^= operandDataI;
}

static void op_concatS() { start_func
    OPERAND_2
    
    ((string*)operand->data.p)->append(operandDataS);
}

static void op_iffalseO() { start_func
    interpretAssert((((opdata & 0xFF0000) >> 16) == OM_OBJECT) || (((opdata & 0xFF0000) >> 16) == OM_ENTITY));
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataP == NULL) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}
static void op_iftrueO() { start_func
    interpretAssert((((opdata & 0xFF0000) >> 16) == OM_OBJECT) || (((opdata & 0xFF0000) >> 16) == OM_ENTITY));
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataP) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}

static void op_iffalseI() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataI == 0) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}
static void op_iftrueI() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataI) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}

static void op_iffalseF() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataF == 0.0) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}
static void op_iftrueF() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (operandDataF != 0.0) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}

static void op_iffalseS() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (*operandDataS == 0) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}
static void op_iftrueS() { start_func
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    ASSERT_2

    grabbers[addrmode = opdata >> 8]();

    // @TODO: + 1 when creating?
    if (*operandDataS) i_p += *((Sint32*)i_p) + 1;
    else ++i_p;
}

static void op_eqO() { start_func
    OPERAND_2CP

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2P == operandDataP);
}
static void op_neO() { start_func
    OPERAND_2CP

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2P != operandDataP);
}

static void op_eqI() { start_func
    OPERAND_2CI
    
    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I == operandDataI);
}
static void op_geI() { start_func
    OPERAND_2CI

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I >= operandDataI);
}
static void op_gtI() { start_func
    OPERAND_2CI

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I > operandDataI);
}
static void op_leI() { start_func
    OPERAND_2CI

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I <= operandDataI);
}
static void op_ltI() { start_func
    OPERAND_2CI

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I < operandDataI);
}
static void op_neI() { start_func
    OPERAND_2CI

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2I != operandDataI);
}


static void op_eqF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_FLOAT;
    stackP.top++->data.i = (operandData2F == operandDataF);
}
static void op_geF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2F >= operandDataF);
}
static void op_gtF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2F > operandDataF);
}
static void op_leF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2F <= operandDataF);
}
static void op_ltF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2F < operandDataF);
}
static void op_neF() { start_func
    OPERAND_2CF

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = (operandData2F != operandDataF);
}

static void op_eqS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) == 0;
}
static void op_geS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) >= 0;
}
static void op_gtS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) > 0;
}
static void op_leS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) <= 0;
}
static void op_ltS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) < 0;
}
static void op_neS() { start_func
    OPERAND_2CS

    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = myStricmp(operandData2S, operandDataS) != 0;
}

static void op_pushI() { start_func
    OPERAND_1
    prepStack(stackP);
    stackP.top->type = STACK_INT;
    stackP.top++->data.i = operandDataI;
}
static void op_pushF() { start_func
    OPERAND_1

    prepStack(stackP);
    stackP.top->type = STACK_FLOAT;
    stackP.top++->data.f = operandDataF;
}
// @TODO: Might be faster using grabbersUse (see op_pushAHO)
static void op_pushS() { start_func
    OPERAND_1

    prepStack(stackP);
    stackP.top->type = STACK_STRING;
    stackP.top++->data.p = new string(operandDataS);
}
static void op_pushAHO() { start_func
    // (must prepstack BEFORE pulling operands anytime we use operand+prepstack together)
    prepStack(stackP);
    ASSERT_1
    grabbersUse[addrmode = opdata >> 8]();
    *stackP.top++ = *operand;
/* @TODO: this code snippet MIGHT be faster, but unlikely
    OPERAND_1
    prepStack(stackP);
    stackP.top->type = STACK_OBJECT;
    stackP.top++->data.p = operandDataP;
    referencers[...];
*/
}

static void op_createE() { start_func
    OPERAND_0

    prepStack(stackP);
    stackP.top->type = STACK_ENTITY;
    stackP.top++->data.p = NULL;
}
static void op_createO() { start_func
    OPERAND_0

    prepStack(stackP);
    stackP.top->type = STACK_OBJECT;
    stackP.top++->data.p = NULL;
}

// @TODO: consolidate into one, if they remain the same; may obsolete grabbersWrite[]
static void op_storeI() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}
static void op_storeF() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}
static void op_storeS() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}
static void op_storeO() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}
static void op_storeA() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}
static void op_storeH() { start_func
    ASSERT_2
    grabbersUse[addrmode = opdata >> 8]();
    grabbersWrite[addrmode = opdata >> 16]();

    *operandWrite = *operand;
}

// @TODO: these have not been fully optimized yet
// further opcode splitting may be in order as well-
// ex: versions that garauntee it's not all/all_other/string/nothing(null)
static void op_getE() { start_func
    ASSERT_3
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 8]();
    prepStack(stackP);
    // could be NULL (nothing)
    if (operandDataP) {
        stackP.top->data = ((Entity*)operandDataP)->varTable[*i_p++];
        stackP.top->type = *i_p++;
        referencers[stackP.top->type](stackP.top);
        stackP.top++;
    }
    else {
        interpretAssert(*i_p <= STACK_BASETYPE);
        // Generate empty of requested type
        // Assumes STACK_UNDEF is 0 otherwise we should have | STACK_UNDEF here
        interpretAssert(STACK_UNDEF == 0);
        copiersUse[*i_p++ << 4]();
        *stackP.top++ = *operand;
    }
}
static void op_getO() { start_func
    ASSERT_3
    interpretAssert(((opdata & 0xFF0000) >> 16) == OM_INT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbers[addrmode = opdata >> 8]();
    prepStack(stackP);
    // could be NULL (nothing)
    if (operandDataP) {
        stackP.top->data = ((ObjectBase*)operandDataP)->members[*i_p++];
        stackP.top->type = *i_p++;
        referencers[stackP.top->type](stackP.top);
        stackP.top++;
    }
    else {
        interpretAssert(*i_p <= STACK_BASETYPE);
        // Generate empty of requested type
        // Assumes STACK_UNDEF is 0 otherwise we should have | STACK_UNDEF here
        interpretAssert(STACK_UNDEF == 0);
        copiersUse[*i_p++ << 4]();
        *stackP.top++ = *operand;
    }
}
static void op_setE() { start_func
    ASSERT_3
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbersUse[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        swap(((Entity*)operandDataP)->varTable[*i_p++], operand->data);
    dereferencers[operand->type](operand);
}
static void op_setO() { start_func
    ASSERT_3
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbersUse[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        swap(((ObjectBase*)operandDataP)->members[*i_p++], operand->data);
    dereferencers[operand->type](operand);
}
// Setting float or int can be optimized due to lack of deref need
// @TODO: but grabbersUse might be faster anyway? dblcheck
static void op_setEF() { start_func
    ASSERT_3
    interpretAssert((((opdata & 0xFF00) >> 8) & OM_BASETYPE) == OM_FLOAT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbers[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        ((Entity*)operandDataP)->varTable[*i_p++].f = operandDataF;
}
static void op_setEI() { start_func
    ASSERT_3
    interpretAssert((((opdata & 0xFF00) >> 8) & OM_BASETYPE) == OM_INT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbers[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        ((Entity*)operandDataP)->varTable[*i_p++].i = operandDataI;
}
static void op_setOF() { start_func
    ASSERT_3
    interpretAssert((((opdata & 0xFF00) >> 8) & OM_BASETYPE) == OM_FLOAT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbers[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        ((ObjectBase*)operandDataP)->members[*i_p++].f = operandDataF;
}
static void op_setOI() { start_func
    ASSERT_3
    interpretAssert((((opdata & 0xFF00) >> 8) & OM_BASETYPE) == OM_INT);
    interpretAssert(((opdata & 0xFF000000) >> 24) == OM_INT);

    grabbers[addrmode = opdata >> 8]();
    // @TODO: doesn't support all/all_other/string
    grabbers[addrmode = opdata >> 16]();
    if (operandDataP)
        ((ObjectBase*)operandDataP)->members[*i_p++].i = operandDataI;
}

static void op_convert() { start_func
    OPERAND_1
    // (do nothing- pulling the operand did the work of converting it)
    // @TODO: Optimization possible, as we're wasting time extracting- grabbersStore?
}
static void op_discard() { start_func
    interpretAssert(stackP.top);
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    interpretAssert(*i_p > 0);
    interpretAssert(*i_p <= stackP.top - stackP.data - entity->getEndOfLocals());
    ASSERT_1
    
    Uint32 toKill = *i_p++;
    do {
        --stackP.top;
        // (return ptrs ok)
        dereferencers[stackP.top->type](stackP.top);
    } while (--toKill);
}
static void op_discardl() { start_func
    interpretAssert(stackP.top);
    interpretAssert(((opdata & 0xFF00) >> 8) == OM_INT);
    interpretAssert(*i_p > 0);
    interpretAssert(*i_p <= stackP.top - stackP.data - entity->getEndOfLocals());
    ASSERT_1
    
    // if debugging enabled, assert everything discarded is 0/1/2/128
#ifdef INTERPRETASSERT
    Uint32 toKill = *i_p++;
    do {
        --stackP.top;
        interpretAssert((stackP.top->type == STACK_INT) ||
                        (stackP.top->type == STACK_FLOAT) ||
                        (stackP.top->type == STACK_UNDEF) ||
                        (stackP.top->type == STACK_CODEPTR));
    } while (--toKill);
#else
    stackP.top -= *i_p++;
#endif
}

//
// Main interpreter loop
//

void Entity::cycle() { start_func
    // Entity must be active and have a script.
    if ((!active) || (!script)) return;

// If asserts on, we need to handle exceptions
#ifdef INTERPRETASSERT
try {
#endif
    
    // Entity must not be stopped.
    interpretAssert(runStatus != RUN_UNINIT);
    if (runStatus == RUN_STOPPED) return;
    if (runStatus == RUN_IDLE) runStatus = RUN_ACTIVE;
    
    /*
    // @TODO: temp chunk for benchmarking
    int startTicks2 = SDL_GetTicks();
    int a = 1;
    int b = 1;
    while (a < 10000000) {
        a = a + 1;
        if (a & 1) b = b + a;
    }
    debugWrite("(in C ticks: %d)", startTicks2 - SDL_GetTicks(), b);
    debugWrite("%d %d", a, b);
    */

#ifndef NDEBUG
    int startTicks = SDL_GetTicks();
    if (debugLevel() & DEBUG_INTERPRET)
        debugWrite("%s [", getName()->c_str());
#endif

    // Prepare state data
    interpretAssert(i);
    i_p = i;
    stackP = stack;
    entity = this;

    do {
#ifndef NDEBUG
        if (debugLevel() & DEBUG_INTERPRET) {
            string toWrite;
            debugBytecode(toWrite, i_p);
            debugWrite("  %s", toWrite.c_str());
        }
#endif
        
        // Perform next opcode; includes pulling operands
        // @TODO: assert valid opcode
        opcodes[(opdata = *i_p++) & 0xFF]();
    
        // Loop continuously until an opcode stops us
        // @TODO: could just be == RUN_ACTIVE if we get rid of INIT state
    } while ((runStatus != RUN_STOPPED) && (runStatus != RUN_IDLE));

    // @TODO: reduce stack size if significant empty space?
    
    // Copy changes in state data back
    i = i_p;
    stack = stackP;

#ifndef NDEBUG
    if (debugLevel() & DEBUG_INTERPRET)
        debugWrite("] %s (ticks: %d)", getName()->c_str(), SDL_GetTicks() - startTicks);
#endif

#ifdef INTERPRETASSERT
}
catch (InterpretException& e) {
    runStatus = RUN_STOPPED;
    i_p = NULL;
    stack = stackP;
    debugWrite("%s", e.details);
    
    // @TODO: Display runtime error
}
#endif
}

void Entity::wipeStack() { start_func
    // wipe stack including locals
    if (stack.top) {
        Uint32 toKill = stack.top - stack.data;
        while (toKill--) {
            --stack.top;
            // (return ptrs ok)
            dereferencers[stack.top->type](stack.top);
        }
    }
    
    stack.top = stack.data;
    endOfLocals = 0;
}

//
// Init/deinit
//

void fillInterpreterTables() { start_func
    for (int i = 0; i < 256; ++i) {
        converters[i] = interpretError;
        copiers[i] = interpretError;
        copiersUse[i] = interpretError;
        grabbers[i] = interpretError;
        grabbersStore[i] = interpretError;
        grabbersWrite[i] = interpretError;
        grabbersUse[i] = interpretError;
        extractors[i] = interpretError;
    }
    for (int i = 0; i < STACK_TYPE_COUNT; ++i) {
        dereferencers[i] = interpretErrorDeref;
        referencers[i] = interpretErrorDeref;
    }

    // Referencing
    referencers[STACK_INT] = refNothing;
    referencers[STACK_FLOAT] = refNothing;
    referencers[STACK_STRING] = refString;
    referencers[STACK_ARRAY] = refArray;
    referencers[STACK_HASH] = refHash;
    referencers[STACK_ENTITY] = refEntity;
    referencers[STACK_OBJECT] = refObject;

    // Dereferencing
    dereferencers[STACK_UNDEF] = derefNothing;
    dereferencers[STACK_INT] = derefNothing;
    dereferencers[STACK_FLOAT] = derefNothing;
    dereferencers[STACK_STRING] = derefString;
    dereferencers[STACK_ARRAY] = derefArray;
    dereferencers[STACK_HASH] = derefHash;
    dereferencers[STACK_ENTITY] = derefEntity;
    dereferencers[STACK_OBJECT] = derefObject;
    dereferencers[STACK_CODEPTR] = derefNothing;
    dereferencers[STACK_RETURNPTR] = derefReturn;
    for (int i = STACK_INDIRECT; i <= STACK_INDIRECT + STACK_BASETYPE; ++i) {
        dereferencers[i] = derefNothing;
    }
    for (int i = STACK_REPLYPTR; i <= STACK_REPLYPTR + STACK_BASETYPE; ++i) {
        dereferencers[i] = derefReply;
    }
    
    // Existing datatypes that we support (conversion)
    // We support conversion from UNDEF so that we can use these
    // arrays to quickly create empty values of any type as well
    int existTypes[8] = {
        STACK_UNDEF,
        STACK_INT,
        STACK_FLOAT,
        STACK_STRING,
        STACK_ARRAY,
        STACK_HASH,
        STACK_ENTITY,
        STACK_OBJECT,
    };
    
    // Start by assuming every conversion "fails", returning empty
    for (int i = 0; i < 8; ++i) {
        converters[(OM_INT << 4) | existTypes[i]] = convertEmptyToInt;
        copiers[(OM_INT << 4) | existTypes[i]] = copyCreateInt;
        copiersUse[(OM_INT << 4) | existTypes[i]] = copyCreateIntUse;
        converters[(OM_FLOAT << 4) | existTypes[i]] = convertEmptyToFloat;
        copiers[(OM_FLOAT << 4) | existTypes[i]] = copyCreateFloat;
        copiersUse[(OM_FLOAT << 4) | existTypes[i]] = copyCreateFloatUse;
        converters[(OM_STR << 4) | existTypes[i]] = convertEmptyToStr;
        copiers[(OM_STR_CONST << 4) | existTypes[i]] = copyCreateStr;
        copiersUse[(OM_STR_CONST << 4) | existTypes[i]] = copyCreateStrUse;
        converters[(OM_ARRAY << 4) | existTypes[i]] = convertEmptyToArray;
        copiers[(OM_ARRAY << 4) | existTypes[i]] = copyCreateArray;
        copiersUse[(OM_ARRAY << 4) | existTypes[i]] = copyCreateArrayUse;
        converters[(OM_HASH << 4) | existTypes[i]] = convertEmptyToHash;
        copiers[(OM_HASH << 4) | existTypes[i]] = copyCreateHash;
        copiersUse[(OM_HASH << 4) | existTypes[i]] = copyCreateHashUse;
        converters[(OM_ENTITY << 4) | existTypes[i]] = convertEmptyToEntity;
        copiers[(OM_ENTITY << 4) | existTypes[i]] = copyCreateEntity;
        copiersUse[(OM_ENTITY << 4) | existTypes[i]] = copyCreateEntityUse;
        converters[(OM_OBJECT << 4) | existTypes[i]] = convertEmptyToObject;
        copiers[(OM_OBJECT << 4) | existTypes[i]] = copyCreateObject;
        copiersUse[(OM_OBJECT << 4) | existTypes[i]] = copyCreateObjectUse;
    }
    
    // Conversions/copies we explicitly support-
    //  int<->float<->string
    // Copy/convert from type to itself also needs to be supported
    // h<->h, a<->a, e<->e, and o<->o are special because the type may match
    // but the subtype needs to be converted

    converters[(OM_INT << 4) | STACK_INT] = doNothing;
    converters[(OM_FLOAT << 4) | STACK_FLOAT] = doNothing;
    converters[(OM_STR << 4) | STACK_STRING] = doNothing;
    
    converters[(OM_INT << 4) | STACK_FLOAT] = convertFloatToInt;
    converters[(OM_INT << 4) | STACK_STRING] = convertStringToInt;
    converters[(OM_FLOAT << 4) | STACK_INT] = convertIntToFloat;
    converters[(OM_FLOAT << 4) | STACK_STRING] = convertStringToFloat;
    converters[(OM_STR << 4) | STACK_INT] = convertIntToString;
    converters[(OM_STR << 4) | STACK_FLOAT] = convertFloatToString;
    
    // Copiers know their value will be unmodified, so if copier
    // needs to do nothing, we can just leave the old reference alone.
    copiers[(OM_INT << 4) | STACK_INT] = extractInt;
    copiers[(OM_FLOAT << 4) | STACK_FLOAT] = extractFloat;
    copiers[(OM_STR_CONST << 4) | STACK_STRING] = extractStringConst;
    
    copiers[(OM_INT << 4) | STACK_FLOAT] = copyFloatToInt;
    copiers[(OM_INT << 4) | STACK_STRING] = copyStringToInt;
    copiers[(OM_FLOAT << 4) | STACK_INT] = copyIntToFloat;
    copiers[(OM_FLOAT << 4) | STACK_STRING] = copyStringToFloat;
    copiers[(OM_STR_CONST << 4) | STACK_INT] = copyIntToString;
    copiers[(OM_STR_CONST << 4) | STACK_FLOAT] = copyFloatToString;

    // @TODO: temp, should be actual copy+convert and for hash/array
    // include making a new copy even if no conversion
    copiers[(OM_ENTITY << 4) | STACK_ENTITY] = extractEntity;
    copiers[(OM_OBJECT << 4) | STACK_OBJECT] = extractObject;
    copiers[(OM_ARRAY << 4) | STACK_ARRAY] = extractArray;
    copiers[(OM_HASH << 4) | STACK_HASH] = extractHash;
    
    // Copiers for use expect their result to be assigned somewhere,
    // but not modified before then, so we just leave the old reference alone.
    copiersUse[(OM_INT << 4) | STACK_INT] = doNothing;
    copiersUse[(OM_FLOAT << 4) | STACK_FLOAT] = doNothing;
    copiersUse[(OM_STR_CONST << 4) | STACK_STRING] = copyStringUse;

    copiersUse[(OM_INT << 4) | STACK_FLOAT] = copyFloatToIntUse;
    copiersUse[(OM_INT << 4) | STACK_STRING] = copyStringToIntUse;
    copiersUse[(OM_FLOAT << 4) | STACK_INT] = copyIntToFloatUse;
    copiersUse[(OM_FLOAT << 4) | STACK_STRING] = copyStringToFloatUse;
    copiersUse[(OM_STR_CONST << 4) | STACK_INT] = copyIntToStringUse;
    copiersUse[(OM_STR_CONST << 4) | STACK_FLOAT] = copyFloatToStringUse;

    /* @TODO:
    
    converters[(OM_ENTITY << 4) | STACK_ENTITY] (ensure if subtype doesn't match, = NULL)
    converters[(OM_OBJECT << 4) | STACK_OBJECT] (ensure if subtype doesn't match, = NULL)
    converters[(OM_ARRAY << 4) | STACK_ARRAY]
    converters[(OM_HASH << 4) | STACK_HASH]

    @TODO: copiers, note that copying to an entity/object doesn't need
    to be dereferenced if we choose to just not ref++ it in the first place!
    
    copiersUse needs to always ++ref even if nothing changes (entity/object)
    
    both types of copiers for array/hash ALWAYS create a new copy even if no change.
    
    */
    
    // Grabbers
    grabbers[OM_INT] = grabInt;
    grabbers[OM_FLOAT] = grabFloat;
    grabbers[OM_STR_CONST] = grabStringConst;
    // @TODO: OM_ALL
    // @TODO: OM_ALL_OTHER
    grabbers[OM_NOTHING] = grabNothing;
    grabbers[OM_THIS] = grabThis;
    // @TODO: OM_POINTER?
    
    grabbersUse[OM_INT] = grabIntUse;
    grabbersUse[OM_FLOAT] = grabFloatUse;
    grabbersUse[OM_STR_CONST] = grabStringUse;
    // @TODO: OM_ALL
    // @TODO: OM_ALL_OTHER
    grabbersUse[OM_NOTHING] = grabNothingUse;
    grabbersUse[OM_THIS] = grabThisUse;
    // @TODO: OM_POINTER?
    
    for (int i = OM_STACK; i < OM_STACK + OM_BASETYPE; ++i) {
        grabbersStore[i] = grabStackConvertStore;
        grabbersWrite[i] = grabStackWrite;
    }
    for (int i = OM_LOCAL; i < OM_LOCAL + OM_BASETYPE; ++i) {
        grabbersStore[i] = grabLocalConvertStore;
        grabbersWrite[i] = grabLocalWrite;
    }
    for (int i = OM_GLOBAL; i < OM_GLOBAL + OM_BASETYPE; ++i) {
        grabbersStore[i] = interpretError; // @TODO: global+convert-in-place
        grabbersWrite[i] = interpretError; // @TODO: global+convert-in-place
    }
    for (int i = OM_POP + OM_NO_CONVERT; i < OM_POP + OM_NO_CONVERT + OM_BASETYPE; ++i) {
        grabbers[i] = grabPop;
        grabbersUse[i] = grabPopUse;
    }
    grabbers[OM_POP + OM_NO_CONVERT + OM_INT] = grabPopInt;
    grabbers[OM_POP + OM_NO_CONVERT + OM_FLOAT] = grabPopFloat;
    // @TODO: specialized POP+ConstStr (shouldn't be able to have POP+Str)
    for (int i = OM_STACK + OM_NO_CONVERT; i < OM_STACK + OM_NO_CONVERT + OM_BASETYPE; ++i) {
        grabbers[i] = grabStack;
        grabbersStore[i] = grabStackStore;
        grabbersWrite[i] = grabStackWrite;
        grabbersUse[i] = grabStackUse;
    }
    grabbers[OM_STACK + OM_NO_CONVERT + OM_INT] = grabStackInt;
    grabbers[OM_STACK + OM_NO_CONVERT + OM_FLOAT] = grabStackFloat;
    grabbersUse[OM_STACK + OM_NO_CONVERT + OM_INT] = grabStackNumUse;
    grabbersUse[OM_STACK + OM_NO_CONVERT + OM_FLOAT] = grabStackNumUse;
    grabbersWrite[OM_STACK + OM_NO_CONVERT + OM_INT] = grabStackNumWrite;
    grabbersWrite[OM_STACK + OM_NO_CONVERT + OM_FLOAT] = grabStackNumWrite;
    for (int i = OM_LOCAL + OM_NO_CONVERT; i < OM_LOCAL + OM_NO_CONVERT + OM_BASETYPE; ++i) {
        grabbers[i] = grabLocal;
        grabbersStore[i] = grabLocalStore;
        grabbersWrite[i] = grabLocalWrite;
        grabbersUse[i] = grabLocalUse;
    }
    grabbers[OM_LOCAL + OM_NO_CONVERT + OM_INT] = grabLocalInt;
    grabbers[OM_LOCAL + OM_NO_CONVERT + OM_FLOAT] = grabLocalFloat;
    grabbersUse[OM_LOCAL + OM_NO_CONVERT + OM_INT] = grabLocalNumUse;
    grabbersUse[OM_LOCAL + OM_NO_CONVERT + OM_FLOAT] = grabLocalNumUse;
    grabbersWrite[OM_LOCAL + OM_NO_CONVERT + OM_INT] = grabLocalNumWrite;
    grabbersWrite[OM_LOCAL + OM_NO_CONVERT + OM_FLOAT] = grabLocalNumWrite;
    for (int i = OM_GLOBAL + OM_NO_CONVERT; i < OM_GLOBAL + OM_NO_CONVERT + OM_BASETYPE; ++i) {
        grabbers[i] = interpretError; // @TODO: global+no-convert
        grabbersStore[i] = interpretError; // @TODO: global+no-convert
        grabbersWrite[i] = interpretError; // @TODO: global+no-convert
        grabbersUse[i] = interpretError; // @TODO: global+no-convert
    }
    // @TODO: global+no-convert+int/float
    for (int i = OM_POP + OM_COPY; i < OM_POP + OM_COPY + OM_BASETYPE; ++i) {
        grabbers[i] = grabPopCopy;
        grabbersUse[i] = grabPopCopyUse;
    }
    for (int i = OM_STACK + OM_COPY; i < OM_STACK + OM_COPY + OM_BASETYPE; ++i) {
        grabbers[i] = grabStackCopy;
        grabbersUse[i] = grabStackCopyUse;
    }
    for (int i = OM_LOCAL + OM_COPY; i < OM_LOCAL + OM_COPY + OM_BASETYPE; ++i) {
        grabbers[i] = grabLocalCopy;
        grabbersUse[i] = grabLocalCopyUse;
    }
    for (int i = OM_GLOBAL + OM_COPY; i < OM_GLOBAL + OM_COPY + OM_BASETYPE; ++i) {
        grabbers[i] = interpretError; // @TODO: global+copy-convert
        grabbersUse[i] = interpretError; // @TODO: global+copy-convert
    }
    
    /* @TODO: indirects (unsure which modes will be needed yet)
    
    for (int i = OM_POP + OM_INDIRECT; i < OM_POP + OM_INDIRECT + OM_BASETYPE; ++i) {
        grabbers[i] = interpretError;
        grabbersStore[i] = interpretError;
        grabbersWrite[i] = interpretError;
        grabbersUse[i] = interpretError;
    }
    for (int i = OM_STACK + OM_INDIRECT; i < OM_STACK + OM_INDIRECT + OM_BASETYPE; ++i) {
        grabbers[i] = interpretError;
        grabbersStore[i] = interpretError;
        grabbersWrite[i] = interpretError;
        grabbersUse[i] = interpretError;
    }
    
    */

    // Extractors
    for (int i = 0; i < 256; i += 16) {
        extractors[i + OM_INT] = extractInt;
        extractors[i + OM_FLOAT] = extractFloat;
        // (should never occur) extractors[i + OM_STR] = extractStringConst;
        extractors[i + OM_STR_CONST] = extractStringConst;
        extractors[i + OM_ARRAY] = extractArray;
        extractors[i + OM_HASH] = extractHash;
        extractors[i + OM_ENTITY] = extractEntity;
        extractors[i + OM_OBJECT] = extractObject;
    }
    
    // Opcodes
    opcodes[OP_NOOP] = op_noop;
    opcodes[OP_ADD] = op_addI;
    opcodes[OP_ADDf] = op_addF;
    opcodes[OP_AND] = op_andI;
    opcodes[OP_CONCAT] = op_concatS;
    opcodes[OP_CONVERT] = op_convert;
    opcodes[OP_CREATEa] = interpretError; // @TODO:
    opcodes[OP_CREATEe] = op_createE;
    opcodes[OP_CREATEh] = interpretError; // @TODO:
    opcodes[OP_CREATEo] = op_createO;
    opcodes[OP_DEBUG] = Entity::op_debug;
    opcodes[OP_DISCARD] = op_discard;
    opcodes[OP_DISCARDL] = op_discardl;
    opcodes[OP_DIV] = op_divI;
    opcodes[OP_DIVf] = op_divF;
    opcodes[OP_EQ] = op_eqI;
    opcodes[OP_EQf] = op_eqF;
    opcodes[OP_EQo] = op_eqO;
    opcodes[OP_EQs] = op_eqS;
    opcodes[OP_GE] = op_geI;
    opcodes[OP_GEf] = op_geF;
    opcodes[OP_GEs] = op_geS;
    opcodes[OP_GT] = op_gtI;
    opcodes[OP_GTf] = op_gtF;
    opcodes[OP_GTs] = op_gtS;
    opcodes[OP_IFFALSE] = op_iffalseI;
    opcodes[OP_IFFALSEa] = interpretError; // @TODO:
    opcodes[OP_IFFALSEf] = op_iffalseF;
    opcodes[OP_IFFALSEh] = interpretError; // @TODO:
    opcodes[OP_IFFALSEo] = op_iffalseO;
    opcodes[OP_IFFALSEs] = op_iffalseS;
    opcodes[OP_IFFALSEv] = interpretError; // @TODO:
    opcodes[OP_IFTRUE] = op_iftrueI;
    opcodes[OP_IFTRUEa] = interpretError; // @TODO:
    opcodes[OP_IFTRUEf] = op_iftrueF;
    opcodes[OP_IFTRUEh] = interpretError; // @TODO:
    opcodes[OP_IFTRUEo] = op_iftrueO;
    opcodes[OP_IFTRUEs] = op_iftrueS;
    opcodes[OP_IFTRUEv] = interpretError; // @TODO:
    opcodes[OP_INIT] = Entity::op_init;
    opcodes[OP_JUMP] = op_jump;
    opcodes[OP_LE] = op_leI;
    opcodes[OP_LEf] = op_leF;
    opcodes[OP_LEs] = op_leS;
    opcodes[OP_LT] = op_ltI;
    opcodes[OP_LTf] = op_ltF;
    opcodes[OP_LTs] = op_ltS;
    opcodes[OP_MOD] = op_modI;
    opcodes[OP_MULT] = op_multI;
    opcodes[OP_MULTf] = op_multF;
    opcodes[OP_NE] = op_neI;
    opcodes[OP_NEf] = op_neF;
    opcodes[OP_NEo] = op_neO;
    opcodes[OP_NEs] = op_neS;
    opcodes[OP_OR] = op_orI;
    opcodes[OP_PUSH] = op_pushI;
    opcodes[OP_PUSHa] = op_pushAHO;
    opcodes[OP_PUSHf] = op_pushF;
    opcodes[OP_PUSHh] = op_pushAHO;
    opcodes[OP_PUSHo] = op_pushAHO;
    opcodes[OP_PUSHs] = op_pushS;
    opcodes[OP_PUSHv] = interpretError; // @TODO:
    opcodes[OP_RET] = Entity::op_ret;
    opcodes[OP_SHIFTL] = op_shiftlI;
    opcodes[OP_SHIFTR] = op_shiftrI;
    opcodes[OP_STOP] = Entity::op_stop;
    opcodes[OP_STORE] = op_storeI;
    opcodes[OP_STOREa] = op_storeA;
    opcodes[OP_STOREf] = op_storeF;
    opcodes[OP_STOREh] = op_storeH;
    opcodes[OP_STOREo] = op_storeO;
    opcodes[OP_STOREs] = op_storeS;
    opcodes[OP_STOREv] = interpretError; // @TODO:
    opcodes[OP_SUB] = op_subI;
    opcodes[OP_SUBf] = op_subF;
    opcodes[OP_SUBR] = op_subr;
    opcodes[OP_XOR] = op_xorI;
    opcodes[OP_GETe] = op_getE;
    opcodes[OP_SETe] = op_setE;
    opcodes[OP_GETo] = op_getO;
    opcodes[OP_SETo] = op_setO;
    opcodes[OP_IDLE] = Entity::op_idle;
    opcodes[OP_RETVOID] = Entity::op_retvoid;
    opcodes[OP_SETef] = op_setEF;
    opcodes[OP_SETei] = op_setEI;
    opcodes[OP_SETof] = op_setOF;
    opcodes[OP_SEToi] = op_setOI;

    // Empty operands
    emptyOperand[0].data.i = 0;
    emptyOperand[0].type = STACK_INT;
    emptyOperand[1].data.f = 0.0;
    emptyOperand[1].type = STACK_FLOAT;
    emptyOperand[2].data.p = NULL;
    emptyOperand[2].type = STACK_ENTITY;
    emptyOperand[3].data.p = NULL;
    emptyOperand[3].type = STACK_OBJECT;

    // Init deref array
    for (int i = 0; i < MAX_TODEREF; ++i) {
        toDeref[i].data.i = 0;
        toDeref[i].type = STACK_INT;
    }
}

void interpreterCleanup() { start_func
    // Clear deref array
    for (int i = 0; i < MAX_TODEREF; ++i) {
        dereferencers[toDeref[i].type](toDeref + i);
        toDeref[i].data.i = 0;
        toDeref[i].type = STACK_INT;
    }
}
